﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SlimDX;
using SlimDX.Direct3D9;

namespace Gype.UI.D3D9
{
    internal class RadialGradientBrush : Brush
    {
        public RadialGradientBrush(Rendertarget9 target, RadialGradientDescription desc)
        {
            if (desc.GradientSize.Length() <= 0)
                throw new ArgumentException("Invalid description. GradientEnd == GradientStart");

            if (desc.Stops == null || desc.Stops.Length == 0)
                throw new ArgumentException("Invalid description. Gradient has no stops!");

            InitTexture(target.GraphicsDevice, desc);
        }

        private void InitTexture(Device dev, RadialGradientDescription desc)
        {
            int cx = (int)desc.GradientSize.X;
            int cy = (int)desc.GradientSize.Y;
            List<GradientStop> stopList = desc.Stops.ToList();
            stopList.Sort((g1, g2) =>
                {
                    if (g1.Distance < g2.Distance)
                        return -1;
                    if (g1.Distance > g2.Distance)
                        return 1;

                    throw new ArgumentException("Invalid gradient stops. To stops with the same value!");
                }
            );

            mTexture = new Texture(dev, cx, cy, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);
            List<byte> byteData = new List<byte>();
            for (int i = 0; i < cy; ++i)
            {
                for (int j = 0; j < cx; ++j)
                {
                    Vector2 pos = new Vector2(j, i);
                    pos = pos - desc.Center;
                    float dist = pos.Length() / getEllipseRadius(pos, desc.HorizontalRadius, desc.VerticalRadius);

                    var color = InterpolateColor(dist, stopList);
                    uint colorValue = 0;
                    colorValue |= ((uint)(color.Alpha * 255.0f)) << 24;
                    colorValue |= ((uint)(color.Red * 255.0f)) << 16;
                    colorValue |= ((uint)(color.Green * 255.0f)) << 8;
                    colorValue |= ((uint)(color.Blue * 255.0f)) << 0;
                    byteData.AddRange(BitConverter.GetBytes(colorValue));
                }
            }

            var surf = mTexture.GetSurfaceLevel(0);
            Surface.FromMemory(surf, byteData.ToArray(), Filter.Default, 0, Format.A8R8G8B8, 4 * cx, new System.Drawing.Rectangle(0, 0, cx, cy));
            surf.Dispose();
        }

        private float getEllipseRadius(Vector2 pos, float a, float b)
        {
            float phi = (float)Math.Atan2(pos.Y, pos.X);
            float a2 = a * a;
            float b2 = b * b;
            float sin2 = (float)Math.Pow(Math.Sin(phi), 2);
            float cos2 = (float)Math.Pow(Math.Cos(phi), 2);
            return (a * b) / (float)Math.Sqrt(a2 * sin2 + b2 * cos2);
        }

        private Color4 InterpolateColor(float dist, List<GradientStop> stops)
        {
            if (dist < stops[0].Distance)
                return stops[0].Color;

            if (dist > stops.Last().Distance)
                return stops.Last().Color;

            for (int i = 0; i < stops.Count - 1; ++i)
            {
                if (stops[i].Distance <= dist && stops[i + 1].Distance > dist)
                    return Color4.Lerp(stops[i].Color, stops[i + 1].Color, (dist - stops[i].Distance) / (stops[i + 1].Distance - stops[i].Distance));
            }

            return stops.Last().Color;
        }

        internal override void ApplyToShader<T>(ShaderCompiler<T> shader, SlimDX.Direct3D9.ConstantDescription description)
        {
            shader.SetValue(description, mTexture);
        }

        Texture mTexture;
    }
}
